﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PortalCurriculo.Util.PasswordComplexity;

namespace PortalCurriculo.Util.Security
{
   /// <summary>
   /// Default password policy implementation
   /// </summary>
   public class PasswordPolicy : BasePolicy, IPasswordPolicy
   {
      public PasswordPolicy() : base()
      {
         MinimumPasswordLength = 7;
         MaximumPasswordLength = int.MaxValue;
         MaximumCharacterSequenceCount = 4;
         MinimumPasswordStrengthIndex = PasswordStrengthIndex.Medium;
      }

      #region IPasswordPolicy Members

      /// <summary>
      /// Indicates the minimum password strength index for this policy (see <see cref="PasswordStrengthIndex"/>)
      /// </summary>
      /// <value>The minimum index of the password strength.</value>
      public PasswordStrengthIndex MinimumPasswordStrengthIndex
      {
         get;
         set;
      }

      /// <summary>
      /// Gets or sets the minimum length of the password.
      /// </summary>
      /// <value>The minimum length of the password.</value>
      public int MinimumPasswordLength
      {
         get;
         set;
      }

      /// <summary>
      /// Gets or sets the maximum length of the password.
      /// </summary>
      /// <value>The maximum length of the password.</value>
      public int MaximumPasswordLength
      {
         get;
         set;
      }

      /// <summary>
      /// Gets or sets a value indicating whether [require mixed case].
      /// </summary>
      /// <value><c>true</c> if [require mixed case]; otherwise, <c>false</c>.</value>
      public bool RequireMixedCase
      {
         get;
         set;
      }

      /// <summary>
      /// Gets or sets a value indicating whether [require digits].
      /// </summary>
      /// <value><c>true</c> if [require digits]; otherwise, <c>false</c>.</value>
      public bool RequireDigits
      {
         get;
         set;
      }

      /// <summary>
      /// Gets or sets a value indicating whether [require special characters].
      /// </summary>
      /// <value>
      /// 	<c>true</c> if [require special characters]; otherwise, <c>false</c>.
      /// </value>
      public bool RequireSpecialCharacters
      {
         get;
         set;
      }

      /// <summary>
      /// Gets or sets the maximum count of characters in a sequence
      /// </summary>
      /// <value>The maximum count of characters in a sequence.</value>
      public int MaximumCharacterSequenceCount
      {
         get;
         set;
      }

      /// <summary>
      /// Indicates if the username needs to be additionally supplied to verify the password complexity against
      /// </summary>
      /// <value>
      /// 	<c>true</c> if [require username to check password against]; otherwise, <c>false</c>.
      /// </value>
      public bool RequireUserNameToCheckPasswordAgainst
      {
         get;
         set;
      }

      /// <summary>
      /// The duration of the lockout in minutes.
      /// </summary>
      /// <value>The duration of the lockout.</value>
      /// <remarks>
      /// This security setting determines the number of minutes a locked-out account
      /// remains locked out before automatically becoming unlocked. The available
      /// range is from 0 minutes through 99,999 minutes. If you set the account
      /// lockout duration to 0, the account will be locked out until an
      /// administrator explicitly unlocks it. If an account lockout threshold is
      /// defined, the account lockout duration must be greater than or equal to
      /// the reset time.
      /// </remarks>
      public int LockoutDuration
      {
         get;
         set;
      }

      /// <summary>
      /// Gets or sets the lockout threshold.
      /// </summary>
      /// <value>The lockout threshold.</value>
      /// <remarks>
      /// This security setting determines the number of failed logon attempts that
      /// causes a user account to be locked out. A locked-out account cannot be
      /// used until it is reset by an administrator or until the lockout duration
      /// for the account has expired. You can set a value between 0 and 999 failed
      /// logon attempts. If you set the value to 0, the account will never be locked out.
      /// </remarks>
      public int LockoutThreshold
      {
         get;
         set;
      }

      /// <summary>
      /// Reset account lockout after X minutes
      /// </summary>
      /// <value>The duration of the lockout.</value>
      /// <remarks>
      /// This security setting determines the number of minutes that must
      /// elapse after a failed logon attempt before the failed logon attempt
      /// counter is reset to 0 bad logon attempts. The available range is
      /// 1 minute to 99,999 minutes.
      /// </remarks>
      public int LockoutResetInMinutes
      {
         get;
         set;
      }

      #endregion

      /// <summary>
      /// Strong password policy.
      /// </summary>
      /// <returns></returns>
      public static PasswordPolicy StrongPolicy()
      {
         PasswordPolicy policy = new PasswordPolicy();
         policy.ResetToDefinedPolicyStrength(PasswordStrengthIndex.Strong);

         return policy;
      }

      /// <summary>
      /// Empty password policy.
      /// </summary>
      /// <returns></returns>
      public static PasswordPolicy EmptyPolicy()
      {
         PasswordPolicy policy = new PasswordPolicy();
         policy.ResetToDefinedPolicyStrength(PasswordStrengthIndex.None);

         return policy;
      }


      /// <summary>
      /// Time in minutes the password is allows to be used for after issue/change
      /// </summary>
      /// <value>The password expiry in minutes.</value>
      /// <remarks>
      /// Used to restrict the life of the password.
      /// </remarks>
      public int PasswordExpiryInMinutes
      {
         get;
         set;
      }

      public int MinutesBeforeUserCanChangePasswordAgain
      {
         get;
         set;
      }

      public int PasswordHistoryCountToCheck
      {
         get;
         set;
      }

      /// <summary>
      /// Resets to defined policy strength.
      /// </summary>
      /// <param name="strength">The strength.</param>
      /// <remarks>
      /// Used to set the policy to a predetermined strength. Note since this is destructive
      /// all the <see cref="Name"/>, <see cref="Identifier"/>, <see cref="Description"/> are
      /// also reset to known values
      /// </remarks>
      public void ResetToDefinedPolicyStrength(PasswordStrengthIndex strength)
      {
         MinimumPasswordStrengthIndex = strength;

         switch (strength)
         {
            case PasswordStrengthIndex.None:
               MinimumPasswordLength = 0;
               MaximumPasswordLength = 1000;
               RequireDigits = false;
               RequireMixedCase = false;
               RequireUserNameToCheckPasswordAgainst = false;
               RequireSpecialCharacters = false;
               MaximumCharacterSequenceCount = 1000;
               PasswordExpiryInMinutes = 0;
               MinutesBeforeUserCanChangePasswordAgain = 0;
               PasswordHistoryCountToCheck = 0;
               LockoutDuration = 0;
               LockoutThreshold = 0;
               LockoutResetInMinutes = 0;
               break;
            case PasswordStrengthIndex.Weak:
               MinimumPasswordLength = 4;
               MaximumPasswordLength = 1000;
               RequireDigits = false;
               RequireMixedCase = false;
               RequireUserNameToCheckPasswordAgainst = false;
               RequireSpecialCharacters = false;
               MaximumCharacterSequenceCount = 4;
               PasswordExpiryInMinutes = 360 * 24 * 60;
               MinutesBeforeUserCanChangePasswordAgain = 1;
               PasswordHistoryCountToCheck = 0;
               LockoutDuration = 0;
               LockoutThreshold = 0;
               LockoutResetInMinutes = 0;
               break;
            case PasswordStrengthIndex.Medium:
               MinimumPasswordLength = 6;
               MaximumPasswordLength = 1000;
               RequireDigits = true;
               RequireMixedCase = true;
               RequireUserNameToCheckPasswordAgainst = false;
               RequireSpecialCharacters = false;
               MaximumCharacterSequenceCount = 4;
               PasswordExpiryInMinutes = 90*24*60;
               MinutesBeforeUserCanChangePasswordAgain = 0;
               PasswordHistoryCountToCheck = 10;
               LockoutDuration = 5;
               LockoutThreshold = 5;
               LockoutResetInMinutes = 30;
               break;
            case PasswordStrengthIndex.Strong:
               MinimumPasswordLength = 8;
               MaximumPasswordLength = 1000;
               RequireDigits = true;
               RequireMixedCase = true;
               RequireUserNameToCheckPasswordAgainst = false;
               RequireSpecialCharacters = false;
               MaximumCharacterSequenceCount = 3;
               PasswordExpiryInMinutes = 90*24*60;
               MinutesBeforeUserCanChangePasswordAgain = 120;
               PasswordHistoryCountToCheck = 20;
               LockoutDuration = 30;
               LockoutThreshold = 5;
               LockoutResetInMinutes = 120;
               break;
            case PasswordStrengthIndex.MostStrong:
               MinimumPasswordLength = 12;
               MaximumPasswordLength = 1000;
               RequireDigits = true;
               RequireMixedCase = true;
               RequireUserNameToCheckPasswordAgainst = true;
               RequireSpecialCharacters = true;
               MaximumCharacterSequenceCount = 3;
               PasswordExpiryInMinutes = 30*24*60;
               MinutesBeforeUserCanChangePasswordAgain = 120;
               PasswordHistoryCountToCheck = 20;
               LockoutDuration = 60;
               LockoutThreshold = 3;
               LockoutResetInMinutes = 120;
               break;
            default:
               throw new ArgumentException("Supplied strength is not recognized as enum", "strength");
         }

         return;
      }
   }
}
